package server;


import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.Node;
import org.dom4j.io.SAXReader;
import servlet.HttpServlet;
import sevletpojo.Context;
import sevletpojo.Host;
import sevletpojo.Mapper;
import sevletpojo.Wrapper;
import thread.ConditionThread;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.*;
import java.util.*;
import java.util.concurrent.*;

/**
 * @author 王瑞
 * @description: minicat的启动类
 * @date 2020/3/1  15:14
 */
public class Bootstrap {

    public static final Integer port = 8080;

    int corePoolSize = 10;  //核心线程数
    int maximumPoolSize = 50;  //最大线程数
    long keepAliveTime = 100L;  //线程空闲时间
    TimeUnit unit = TimeUnit.SECONDS;  //空闲时间单位
    BlockingQueue<Runnable> workQueue = new ArrayBlockingQueue<>(50);   //队列  默认50个
    ThreadFactory threadFactory = Executors.defaultThreadFactory();    //指定一个线程工厂类
    RejectedExecutionHandler handler = new ThreadPoolExecutor.AbortPolicy();   //线程丢弃策略

    private ThreadPoolExecutor threadPool = new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime,
            unit, workQueue, threadFactory, handler);

    //MiniCat的初始化方法  这里统一监听的端口是8080
    public void start() throws Exception {
        //这里统一监听的端口是8080
        ServerSocket serverSocket = new ServerSocket(port);
        System.out.println("======>minicat启动，端口为：" + port);
        //loadServlet();
        //初始化mapperList 将各个容器逐层封装
        List<Mapper> mapperList = initMapper();
        while (true) {
            Socket socket = serverSocket.accept();
            //自定义的线程
            ConditionThread conditionThread = new ConditionThread(socket, mapperList);
            threadPool.execute(conditionThread);
        }

    }

    //用dom4j解析指定部署项目的web.xml文件 并把解析出来的映射路径和Servlet保存在map集合中
    private Map<String, HttpServlet> getServletMap(File file) {
        Map<String, HttpServlet> sMap = new HashMap<>();
        //将web.xml文件加载成流存放在内存中方便解析
        //InputStream resourceAsStream = this.getClass().getClassLoader().getResourceAsStream("web.xml");
        SAXReader saxReader = new SAXReader();
        try {
            //读取文件流
            Document read = saxReader.read(file);
            //获得根元素
            Element rootElement = read.getRootElement();
            //获得每一个servlet标签
            List<Element> list = rootElement.selectNodes("//servlet");

            //遍历每一个servlet标签元素
            for (Element element : list) {
                //获取servlet标签的servlet-name元素和servlet-class元素
                Element servletNameElemet = (Element) element.selectSingleNode("servlet-name");
                Element servletCalssElemet = (Element) element.selectSingleNode("servlet-class");

                //获取这两个元素的值
                String servletName = servletNameElemet.getStringValue();
                String calssName = servletCalssElemet.getStringValue();

                //利用xpath表达式获取与servletName相对应的servlet-Mapping元素
                Element servletMappingElemet = (Element) rootElement.selectSingleNode("/web-app/servlet-mapping[servlet-name='" + servletName + "']");

                //获取servletMapping的url-pattern路径
                Element urlPatternElemnt = (Element) servletMappingElemet.selectSingleNode("url-pattern");
                //获取url-pattern的值
                String urlPattern = urlPatternElemnt.getStringValue();

                //把url-pattern作为key    calssName反射成对象作为值  一一对应 存到servletMap中  完成servletMap初始化
                String path = file.getPath();
                /*E:\yingdian\webapps\demo2\web.xml*/
                String name = file.getName();
                path = path.replaceAll("\\\\","/");
                String classPath = path.replace(name, "server/");
                //URLClassLoader类加载器加载文件的时候 要加上file:/前缀 不然会报"拒绝访问"
                classPath = "file:/"+classPath;

                //URLClassLoader需要传递一个URL数组 于是把需要加载的URL对象放到数组中
                URL url1 = new URL(classPath);
                URL[] urls = new URL[] {url1};
                //初始化一个url类加载器
                URLClassLoader classLoader = new URLClassLoader(urls);
                //加载从web.xml中解析出来的Servlet的classname
                Class<?> aClass1 = classLoader.loadClass(calssName);
                // 反射 来实例化Servlet
                HttpServlet o = (HttpServlet)aClass1.newInstance();

                //键值对形式保存到map中
                sMap.put(urlPattern, o);
            }


        } catch (DocumentException | ClassNotFoundException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (MalformedURLException e) {
            e.printStackTrace();
        }

        return sMap;

    }

    /*
    * 初始化mapper对象
    * */
    private List<Mapper> initMapper() throws DocumentException {
        List<Mapper> mapperList = new ArrayList<>();
        //解析MiniCat的server.xml配置文件
        InputStream resourceAsStream = this.getClass().getClassLoader().getResourceAsStream("server.xml");
        //dom4j
        SAXReader saxReader = new SAXReader();
        Document read = saxReader.read(resourceAsStream);
        Element rootElement = read.getRootElement();
        //解析Service
        List<Element> list = rootElement.selectNodes("//Service");

        //遍历Service元素
        for (Element element : list) {
            //准备封装mapper对象
            Mapper mapper = new Mapper();
            List<Host> hostList = new ArrayList<>();
            //Host集合解析封装
            List<Element> hostElementList = element.selectNodes("//Host");
            for (Element hostElement : hostElementList) {
                Host host = new Host();
                //获取项目的部署路径
                String appBasePath = hostElement.attributeValue("appBase");
                //解析项目的部署路径 项目可能有多个 封装成Context集合
                List<Context> contextList = getContextByPath(appBasePath);

                //将Context集合设置给host
                host.setContextList(contextList);

                hostList.add(host);
            }
            //将host集合设置给mapper
            mapper.setHostList(hostList);
            mapperList.add(mapper);
        }
        return mapperList;
    }

    /*
    *
    *   根据项目的部署路径 解析这个文件夹  封装Context集合
    * */
    public List<Context> getContextByPath(String path) {
        List<Context> contextList = new ArrayList<>();

        File file = new File(path);
        if (!file.exists()) {
            return contextList;
        } else {
            //如果E:/yingdian/webapps这个文件夹存在 就找项目文件夹
            File[] files = file.listFiles();
            for (File file1 : files) {
                Context context = new Context();
                //获得项目的名称 设置给context的name属性
                String name1 = file1.getName();
                context.setName(name1);
                List<Wrapper> wrapperList = new ArrayList<>();
                File[] files1 = file1.listFiles();
                for (File file2 : files1) {
                    String name = file2.getName();
                    //找到项目的web.xml配置文件
                    if (name.equals("web.xml")) {
                        //URLClassLoader classLoader = new URLClassLoader();
                        //解析项目的web.xml的配置文件 把解析出来的rulPatern 和对应的servlet对象封装成map集合返回过来
                        Map<String, HttpServlet> servletMap = getServletMap(file2);
                        Set<Map.Entry<String, HttpServlet>> entries = servletMap.entrySet();
                        //遍历解析出来的web.xml文件解析出来的map集合  把map的key  value分别设置给wrapper类 完成Wrapper集合封装
                        for (Map.Entry<String, HttpServlet> entry : entries) {
                            String key = entry.getKey();
                            HttpServlet value = entry.getValue();
                            Wrapper wrapper = new Wrapper();
                            wrapper.setName(key);
                            wrapper.setServlet(value);
                            wrapperList.add(wrapper);
                        }
                    }
                }
                context.setWrapperList(wrapperList);
                contextList.add(context);
            }
        }

        return contextList;
    }

    public static void main(String[] args) throws IOException {
        Bootstrap bootstrap = new Bootstrap();
        try {
            bootstrap.start();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
